package it.unibo.refolding.pico;

import it.unibo.refolding.common.CommonThings;
import it.unibo.refolding.pico.PicoDataAnalyser.OffsetAndDeflections;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;

import com.google.common.base.Charsets;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class PeakSlopesHelper {

  private static final Logger logger = Logger.getLogger(PeakSlopesHelper.class.getSimpleName());

  public static Option createPeakSlopesDirOption() {
    Option peakSlopesDirOption = new Option(
      "peakSlopesDir", true, "directory with .psi files containing peakSlopeIndices");
    peakSlopesDirOption.setRequired(true);
    peakSlopesDirOption.setType(String.class);
    return peakSlopesDirOption;
  }

  public static PeakSlopes readOrComputeAndWritePeakSlopesOrNull(
    PlainPicoData plainPicoData, double[] tipDistanceM, OffsetAndDeflections offsetAndDeflections,
    double empiricalBaseline, double minimumPeakForceN,
    double minPeakForceRelativeToEmpiricalBaselineN, double medianAbsoluteDeviationLowerBoundN,
    double leftMostlyHigherWindowSizeM, double leftMostlyHigherFraction, double leftMostlyHigherN,
    double rightMuchHigherThanAllN, double rightMuchHigherN, double atMostOnePeakWindowM,
    Path peakSlopesDir, String filename) throws IOException {
    try {
      return readOrComputeAndWritePeakSlopes(
        plainPicoData, tipDistanceM, offsetAndDeflections, empiricalBaseline, minimumPeakForceN,
        minPeakForceRelativeToEmpiricalBaselineN, medianAbsoluteDeviationLowerBoundN,
        leftMostlyHigherWindowSizeM, leftMostlyHigherFraction, leftMostlyHigherN,
        rightMuchHigherThanAllN, rightMuchHigherN, atMostOnePeakWindowM, filename, peakSlopesDir);
    } catch (IllegalArgumentException | IllegalStateException ex) {
      logger.log(
        Level.WARNING, "Could not analyse {0} because of {1}.", new Object[] {filename, ex});
      return null;
    }
  }

  public static Path peakSlopesFile(Path dir, String filename) {
    return dir == null ? null : dir.resolve(filename + ".psi");
  }

  private static PeakSlopes readOrComputeAndWritePeakSlopes(
    PlainPicoData plainPicoData, double[] tipDistanceM, OffsetAndDeflections offsetAndDeflections,
    double empiricalBaseline, double minimumPeakForceN,
    double minPeakForceRelativeToEmpiricalBaselineN, double medianAbsoluteDeviationLowerBoundN,
    double leftMostlyHigherWindowSizeM, double leftMostlyHigherFraction, double leftMostlyHigherN,
    double rightMuchHigherThanAllN, double rightMuchHigherN, double atMostOnePeakWindowM,
    String filename, Path peakSlopesDir) throws IOException {
    final PeakSlopes peakSlopes;
    if (peakSlopesDir == null) {
      peakSlopes = PicoDataAnalyser.findStartSlopeIndices(
        plainPicoData, tipDistanceM, offsetAndDeflections, minimumPeakForceN, empiricalBaseline,
        minPeakForceRelativeToEmpiricalBaselineN, medianAbsoluteDeviationLowerBoundN,
        leftMostlyHigherWindowSizeM, leftMostlyHigherFraction, leftMostlyHigherN,
        rightMuchHigherThanAllN, rightMuchHigherN, atMostOnePeakWindowM);
    } else {
      Path peakSlopesFile = peakSlopesFile(peakSlopesDir, filename);
      if (Files.isRegularFile(peakSlopesFile)) {
        peakSlopes = extractPeakSlopes(peakSlopesFile);
      } else {
        peakSlopes = PicoDataAnalyser.findStartSlopeIndices(
          plainPicoData, tipDistanceM, offsetAndDeflections, minimumPeakForceN, empiricalBaseline,
          minPeakForceRelativeToEmpiricalBaselineN, medianAbsoluteDeviationLowerBoundN,
          leftMostlyHigherWindowSizeM, leftMostlyHigherFraction, leftMostlyHigherN,
          rightMuchHigherThanAllN, rightMuchHigherN, atMostOnePeakWindowM);
        writeToFile(peakSlopes, peakSlopesFile);
      }
    }
    return peakSlopes;
  }

  public static PeakSlopes extractPeakSlopes(Path peakSlopesFile) throws IOException {
    String s = new String(Files.readAllBytes(peakSlopesFile), Charsets.ISO_8859_1);
    return PeakSlopes.parseRepr(s.trim());
  }

  public static void writeToFile(PeakSlopes peakSlopes, Path file) throws IOException {
    Files.write(file, (peakSlopes.repr() + "\n").getBytes(Charsets.ISO_8859_1));
  }

  public static void writeToFile(List<PeakSlopes> peakSlopes, Path file) throws IOException {
    Files.write(file, (PeakSlopes.repr(peakSlopes) + "\n").getBytes(Charsets.ISO_8859_1));
  }

  public static Path createPeakSlopesDirIfNeeded(Option dirOption, CommandLine line) {
    String dir = line.getOptionValue(dirOption.getOpt());
    return CommonThings.createDirIfNeeded(dir);
  }

  public static List<PeakSlopes> extractPeakSlopesList(Path peakSlopesFile) throws IOException {
    String s = new String(Files.readAllBytes(peakSlopesFile), Charsets.ISO_8859_1);
    return PeakSlopes.parseListRepr(s.trim());
  }

  public static List<PeakSlopes> fillUpPeakSlopes(
    List<PlainPicoData> picoDataList, PeakSlopes prototype, List<PeakSlopes> ps) {
    // For now, FetchRefoldBreak only analyses the fetch.
    return Collections.unmodifiableList(Lists.newArrayList(Iterables.concat(
      ps, Arrays.asList(PeakSlopes.deepCopies(prototype, picoDataList.size() - ps.size())))));
  }
}
